package uhasselt.ontologyplugin;

import uhasselt.ontologyplugin.ToSkosTransformer;
import net.sf.saxon.s9api.XdmNode;

import net.sf.saxon.s9api.Processor;
import net.sf.saxon.s9api.DocumentBuilder;
import net.sf.saxon.s9api.XdmNode;
import net.sf.saxon.s9api.XQueryCompiler;
import net.sf.saxon.s9api.XQueryExecutable;
import net.sf.saxon.s9api.XQueryEvaluator;
import net.sf.saxon.s9api.QName;

/**
 * When deriving from this class, please keep in mind that a lot of functions should be called manually, 
 * which in normal circumstances should be called automatically in proper OO designs i.e. initialise should normally be called 
 * in the constructor. However, this was not done to increase flexibility and performance, which was needed to implement the 
 * required customizability.
 *
 * You always have to call initialise right after constructing an object derived from this class!
 * You have to transform the XML documents yourself right before returning the XdmNode, by calling the appropriate method of _transformer
 * You have to call addRequiredRelations yourself if necessary.
 */
public abstract class OntologyReader {
	
	protected ToSkosTransformer _transformer = null;
	protected RelationName[] _requiredRelations = null;
	
	public enum SearchMode{
		CONTAINS,EXACT,STARTSWITH,ENDSWITH;
	}
	
	public enum RelationName {
		BROADER("broader"), 
		NARROWER("narrower"), 
		RELATED("related"),
		SEMANTICRELATION("semanticRelation"), 
		BROADERTRANSITIVE("broaderTransitive"), 
		NARROWERTRANSITIVE("narrowerTransitive");
		
		private String text;
		
		RelationName(String text) {
			this.text = text;
		}
		
		public String getText() {
			return this.text;
		}

		public static RelationName fromString(String text) {
			if (text != null) {
				for (RelationName b : RelationName.values()) {
					if (text.equalsIgnoreCase(b.text)) {
						return b;
					}
				}
			}
			return null;
		}


	}	
	/**
	 * @param uri The uri of the keyword
     * @param relName The relation name
     * @return The relations of the keyword, e.g. the broader terms or the narrower terms, this is returned in format of the ontology
	 */
	public abstract XdmNode getRelations(String uri,RelationName relName,String lang);
	
	/**
	 * @param searchTerm
	 * @param mode The search mode
	 * @param lang The language
	 * @return The search results in the format of the ontology
	 */
	public abstract XdmNode search(String searchTerm,SearchMode mode,String lang);
	
	/**
	 * @return The top level terms in the format of the ontology
	 */
	public abstract XdmNode getTopLevelTerms(String lang);
	
	/**
	 * @param uri The `URI' of the concept
     * @return The concept with `URI' uri
	 */
	public abstract XdmNode getConcept(String uri,String lang);
	
	/**
	 * Initialise a specific implementation with details provided by the ontologySettings.properties file
	 * @param reader PropertyReader managing the properties in the ontologySettings.properties file
	 * @param thesaurus The name of the thesaurus, needed to query the ontologySettings.properties file
	 */
	public abstract void initialise(PropertyReader reader, String thesaurus);
	
	
	/**
	 * @return Set the transformer which will be used to transform the document to skos
	 */
	protected void setTransformer(ToSkosTransformer transformer){
		_transformer = transformer;
	}
	
	/**
	 * @return The toSkos transformer for this reader
	 */
	protected ToSkosTransformer getTransformer(){
		return _transformer;
	}
	
	
	/**
	 * Adds the relations to the concept with the given uri in the given language
	 * Note that this function has to be called manually if you want to add the required relations to your concept
	 * This was done for performance enhancement
	 * Furthermore, the skos xml document can only contain one concept, the concept with the given uri!
	 * 
	 * TODO: Maybe extend to allow more than one concept
	 * @param n root of the skos document
	 * @param uri Concept uri
	 * @param lang The language
	 */
	protected XdmNode addRequiredRelations(XdmNode n,String uri,String lang){
		if(_requiredRelations != null){
			try{
				String queryString = "";
				String relNames = "";
			
				Processor processor =ToSkosTransformer.getProcessorInstance();
				DocumentBuilder documentBuilder = processor.newDocumentBuilder();
				XQueryCompiler xQueryCompiler = processor.newXQueryCompiler();
				queryString +="declare namespace skos = \"http://www.w3.org/2004/02/skos/core#\";";
				queryString +="declare namespace rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\";";
				queryString +="declare variable $queryResult external;";
		
				for(int i = 0;i < _requiredRelations.length;++i){
					queryString +="declare variable $" + _requiredRelations[i].getText() +" external; ";
					relNames += "$" + _requiredRelations[i].getText() + ((i != _requiredRelations.length-1) ? "," : "");
				}
				queryString += "<rdf:RDF><skos:Concept rdf:about=\""+uri+"\">{";
				queryString += "$queryResult//skos:Concept[1]/*}";
				for(int i = 0;i < _requiredRelations.length;++i){
					queryString += "<skos:"+_requiredRelations[i].getText()+">{$"+_requiredRelations[i].getText()+"//skos:Concept}</skos:"+_requiredRelations[i].getText()+">";
				}
				queryString += "</skos:Concept></rdf:RDF>";
	
				XQueryExecutable xQueryExecutable = xQueryCompiler.compile(queryString);
				XQueryEvaluator xQueryEvaluator = xQueryExecutable.load();
				xQueryEvaluator.setExternalVariable(new QName("queryResult"),n);
				for(int i = 0;i < _requiredRelations.length;++i){
					XdmNode temp = getRelations(uri,_requiredRelations[i],lang);	
					xQueryEvaluator.setExternalVariable(new QName(_requiredRelations[i].getText()), temp);
				}
				return RemoveNameSpaces.getInstance().transform((XdmNode)xQueryEvaluator.evaluateSingle());
			}catch (Exception e){
				e.printStackTrace();
			}
		}
		return null;
	}
	/**
	 * READS THE thesaurus.requiredrelations property! If this property is not set it will not add any relations!
 	 *
	 * Initialises the required relations array (which is used to add required relations to concepts when needed)
	 * @param reader The property reader
	 * @param thesaurus The thesaurus 
	 */
	protected void initRequiredRelations(PropertyReader reader,String thesaurus){
		String reqRel = reader.getValue(thesaurus +".requiredrelations");
		if(reqRel != null){
			String[] temp =  reqRel.split(",");
			_requiredRelations = new RelationName[temp.length];
			for(int i = 0;i<temp.length;++i){
				_requiredRelations[i] = RelationName.fromString(temp[i]);
			}
		}
	}
	
}